Rechnerorganisasion TUT 11
was ist ein nop-Befehl
ursprüngliches Proplem bei Eintaktprozessor: Abschnitten im Eintaktprozessor werden nicht vollständig ausgenutzt.
Idee: wir fangen an, Befehle auszuführen, bevor der vorherige Befehl fertig ist.
wir haben 5 Abschnitte: Instruction Fetch, Instruction Decode, Execute, Memory (Read/Write), Write Back
diese trennen die verschiedenen Stufen
aber warum brauchen wir diese Register wirklich?
=> die Pipeline-Register halten die Eingangsdaten (für eine Stufe) und warten, bis der nächste Takt anfängt (Synchronisation)
Datenkonflikte (Data Hazard)
Register-Werte fehlen, wenn diese gebraucht werden
wenn ein Befehl das Ergebnis von dem vorherigen Befehl braucht, dann ist der vorherige Befehl noch nicht fertig!!
Die Daten des vorherigen Befehls sind nicht da
Die gelesen Register vom nächsten Befehl (weiter links in der Pipeline) sind noch nicht upgedatet, also falsch
wir können das richtige Ergebnis erst aus dem Register laden, während das Ergebnis geschrieben wird
warum?
wie kann machen, damit Data Hazards weniger Zeit verschwenden?
Steuerkonflikte (Control Hazard)
die Sprungentscheidung, Sprungadresse fehlt, wenn wir diese bereits brauchen
der Kontrollfluss (also die Befehlsausführung) ist verzögert (ist noch nicht entschieden)
wir können Befehle ausführen, die eigentlich nicht ausgeführt werden sollen
wie schneller machen?
Strukturelle Konflikte (Structural Hazard) (nennen wir nur wegen der Vollständigkeit)
wie verhindern (entschärfen) wir die Hazards?
a)
0 addi *$t0* ,$a0 ,4
1 addi *$t1* ,$a1 ,4
2 sub $t2 ,*$t0* ,*$t1*
3 sll *$t3* ,$a2 ,2
4 add *$t4* ,$t0 ,*$t3*
5 add $t5 ,$t1 ,*$t3*
6 sw $t2 ,0( *$t4* )
b)
0 addi $t0 ,$a0 ,4
1 addi $t1 ,$a1 ,4
1.2 nop
1.3 nop
2 sub $t2 , $t0 , $t1
3 sll $t3 ,$a2 ,2
3.1 nop
3.2 nop
4 add $t4 ,$t0 , $t3
5 add $t5 ,$t1 , $t3
5.1 nop
6 sw $t2 ,0( $t4 )
c)
a)
Speicherzugriff = 200ps, ALU-Operation = 100ps, Registerzugriff = 50ps
Benchmark (Programm, um die Ausführungszeit zu messen)
Annahmen: gleiche Anzahl an Befehle für beide Prozessoren
Jeder Prozessor hat eine (eigene) konstante Taktzeit, T_SC = Taktdauer des Eintaktprozessor (Single Cycle), T_PP = Taktdauer für Pipeline-Prozessor
es gibt keine Data Hazards mehr (wurden entfernt durch Umordnung)
es gibt aber alle Control-Hazards noch! -> jeder Sprungbefehl dauert 4 Takte! Der Prozessor hält bei Sprungbefehlen einfach an!
Speicherbefehle -> 12% (1 Takt pro Speicherbefehl weil keine Datenkonflikte Data Hazards)
ALU-Befehle -> 72% (1 Takt pro Speicherbefehl weil keine Datenkonflikte)
Sprung-Befehle -> 16% (4 Takte pro Sprungbefehl, wegen den Steuerkonflikten Control Hazards)
wozu die Informationen? Damit wir die Schnelligkeit des Pipeline-Prozessors berechnen können. Eintaktprozessor führt jeden Befehl gleichschnell aus
unbekannte Anzahl N an Befehlen
Pipeline ist zu Beginn der Benchmark gefüllt
Speedup berechnen, S = Vergleichszeit t_SC / (betrachtete Zeit) t_PP -> wie viel Faktor schneller ist der betrachtete Prozessor
t_SC = Ausführungszeit Eintaktprozessor = (Anzahl an Takte) · (Taktdauer) = (N · CPI_SC) · T_SC
t_PP = Ausführungszeit Pipelineprozessor = (Anzahl an Takte) · (Taktdauer) = (N · CPI_PP) · T_PP
CPI = Befehlsdauer in Takten = wie lange braucht ein Befehl an Takten zur Ausführung
T = Taktdauer, wie viel Sekunden braucht ein Takt
S = (N · CPI_SC) · T_SC / ((N · CPI_PP) · T_PP) = 600ps / (1.48 * 200ps) = 2,027027027… = 2 + 27/999
b) wie können wir den Pipeline-Prozessor schneller machen? siehe Aufgabe 1
c) Was ist, wenn wir nur die ALU schneller machen, um 25% ?
ALU-Operation braucht nur 75ps
-> T_SC = 2·200ps + 75ps + 2·50ps = 575ps
T_PP ändert sich nicht!! Immernoch die gleichen Hazards, und Speicherbefehle dauern immernoch 200ps
=> der Speedup wird schlechter, weil der Pipeline-Prozessor nicht profitiert
=> Pipeline wird nur schneller, wenn man die am längsten dauernde Stufe optimiert.
Aufgabe 3
a) Verbesserung ist, dass man die EX und MEM-Stufe zusammenlegen kann. Die Pipeline wird kürzer. Load-Use-Konflikte sind einen Takt schneller (0 statt 1 Takte Verzögerung).
b)
Verbesserungen gibt es, wenn Speicherfefehle keinen Offset brauchen
das ist der Fall bei Array-Zugriffen in Schleifen oder Integer-Pointer
alle anderen Zugriffe brauchen einen Offset
wenn man einen konstanten Offset braucht, wird man verlangsamt
der Offset muss in Extra-Befehl berechnet werden
der zusätzliche Befehl wird nur durch kürzere Load-Use-Hazards kompensiert
verbraucht ein Register (erhöht den Registerdruck), sowie zusätzlichen Platz im Befehlsspeicher (Cache-Verschmutzung)
in Praxis brauchen wahrscheinlich mehr Befehle einen konstanten Offset als keinen
alle struct-, Objekt-Zugriffe brauchen einen; häufiger Spezialfall: Stack-Frames
häufig in Praxis, vor allem Stack Frames, da es viel mehr innere Funktionsaufrufe mit Stack als Blatt-Funktionsaufrufe ohne Stack gibt.